package org.etherframework.tests.utils
{
	import flash.display.DisplayObject;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.system.ApplicationDomain;
	import flash.utils.Dictionary;
	
	import mx.core.FlexGlobals;
	import mx.core.IVisualElement;
	import mx.utils.UIDUtil;
	
	import org.etherframework.IEther;
	import org.etherframework.controller.Commands;
	import org.etherframework.common.IDestroyable;
	import org.etherframework.common.command.ICommands;
	import org.etherframework.common.mediation.IMediations;
	import org.etherframework.common.wire.IWiring;
	import org.etherframework.core.$Globals;
	import org.etherframework.core.EtherConfig;
	import org.etherframework.core.GlobalConfig;
	import org.etherframework.core.IModule;
	import org.etherframework.core.Module;
	import org.etherframework.core.bridge.Bridge;
	import org.etherframework.core.bridge.BridgeConfig;
	import org.etherframework.core.bridge.BridgeFacades;
	import org.etherframework.core.bridge.BridgeInteraction;
	import org.etherframework.core.bridge.BridgeProcessor;
	import org.etherframework.core.bridge.Controllers;
	import org.etherframework.core.bridge.IBridge;
	import org.etherframework.core.bridge.IObjectController;
	import org.etherframework.core.common.IControllerFactory;
	import org.etherframework.core.common.IInstanceFactory;
	import org.etherframework.core.common.RootDispatcher;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.Contexts;
	import org.etherframework.core.context.Default;
	import org.etherframework.core.manager.DestructionManager;
	import org.etherframework.core.module.IModuleContainer;
	import org.etherframework.core.module.IModulePart;
	import org.etherframework.core.module.ModuleConfig;
	import org.etherframework.core.module.ModuleContainer;
	import org.etherframework.core.module.ModuleController;
	import org.etherframework.core.utils.ClassUtils;
	import org.etherframework.core.utils.Dispatcher;
	import org.etherframework.dependency.IDependencies;
	import org.etherframework.mediation.Mediations;
	import org.etherframework.mediator.IMediators;
	import org.etherframework.wire.ProcessorFactory;
	import org.etherframework.wire.Wiring;
	import org.etherframework.wire.processors.Inject;
	import org.osmf.containers.IMediaContainer;
	
	import spark.components.Group;

	public final class TestUtility implements IControllerFactory, IInstanceFactory
	{
		private var _dictionary:Dictionary = new Dictionary();
		private var _moduleContainer:IModuleContainer;
		
		public function createModule(config:EtherConfig = null, 
											view:DisplayObject = null, 
											globalDispatcher:IEventDispatcher = null, 
											localDispatcher:IEventDispatcher = null, 
											applicationDomain:ApplicationDomain = null, 
											moduleContainer:IModuleContainer = null, 
											autoStart:Boolean = true):IModule
		{
			return createModuleInfo(config, view, globalDispatcher, localDispatcher, 
				applicationDomain, moduleContainer, autoStart).module;
		}
		
		public function createModuleInfo(config:EtherConfig = null, 
									  view:DisplayObject = null, 
									  globalDispatcher:IEventDispatcher = null, 
									  localDispatcher:IEventDispatcher = null, 
									  applicationDomain:ApplicationDomain = null, 
									  moduleContainer:IModuleContainer = null, 
									  autoStart:Boolean = true):ModuleInfo
		{
			$Globals.config = new GlobalConfig();
			
			if (!config)
			{
				config = new EtherConfig();
				config.initializeModules();
				Wiring.initialize(Vector.<ProcessorFactory>([new ProcessorFactory(Inject)]));
				Commands.initialize();
				Mediations.initialize();
			}
			else
			{
				config.initializeModules();
			}
			
			if (!view)
				view = new Group();
			if (!globalDispatcher)
				globalDispatcher = new EventDispatcher();
			if (!localDispatcher)
				localDispatcher = new EventDispatcher();
			if (!applicationDomain)
				applicationDomain = ApplicationDomain.currentDomain;
			
			_moduleContainer = moduleContainer || getModuleContainer();
			
			const gConfig:GlobalConfig = $Globals.config;
			const bConfig:BridgeConfig = new BridgeConfig();
			const bridge:Bridge = new Bridge();
			
			bConfig.module = new Module(view);
			bConfig.contexts = new Contexts();
			bConfig.config = config.clone();
			bConfig.facade = new BridgeFacades(new Default(), bConfig.contexts);
			bConfig.dispatcher = new Dispatcher(localDispatcher);
			bConfig.destructionManager = new DestructionManager();
			bConfig.interaction = new BridgeInteraction();
			bConfig.controllers = new Controllers(bridge);
			bConfig.instanceFactory = gConfig.instanceFactory || this;
			bConfig.processor = new BridgeProcessor(bConfig.facade);
			bConfig.localDispatcher = localDispatcher;
			bConfig.globalDispatcher = globalDispatcher;
			bConfig.applicationDomain = applicationDomain;
			bConfig.defaultContext = new Default();
			BridgeInteraction(bConfig.interaction).initialize(bridge, bConfig.dispatcher);
			bConfig.processor.initialize(bConfig.instanceFactory, $Globals.config.getObjectProcessors());
			bridge.initialize(bConfig);
			
			const ether:IEther = bridge.createEther();
			const moduleConfig:ModuleConfig = createModuleConfig(bridge, ether, gConfig);
			
			bConfig.module.initialize(moduleConfig);
			BridgeFacades(bConfig.facade).initialize(bConfig.module, bridge, moduleConfig);
			
			const controller:IObjectController = IObjectController(bridge.process(bConfig.module, ether, this));
			_moduleContainer.addController(controller);
			
			_dictionary[bConfig.module] = bridge;
			
			if (autoStart)
			{
				bConfig.module.start();
				bConfig.module.started = true;
			}
			
			return new ModuleInfo(bConfig.module, bridge, bConfig.controllers, _moduleContainer); 
		}
		
		public function createEther(module:IModule, context:Context = null):IEther
		{
			if (!_dictionary[module])
				throw new ArgumentError("IModule does not have a bridge.");
			
			const bridge:IBridge = _dictionary[module] as IBridge;
			return bridge.createEther(context || new Default());
		}
		
		public function createController(target:Object, ether:IEther, bridge:IBridge):IObjectController
		{
			return new ModuleController(IModule(target), ether, bridge);
		}
		
		public function createInstance(generator:Class, args:Array, ether:IEther):Object
		{
			return ClassUtils.instantiate(generator, args);
		}
		
		public function createHierarchy(configs:Vector.<EtherConfig>, views:Vector.<DisplayObject> = null):ModuleHierarchy
		{
			var parentInfo:ModuleInfo;
			var modules:Vector.<ModuleInfo> = new Vector.<ModuleInfo>();
			const domain:ApplicationDomain = ApplicationDomain.currentDomain;
			const gDispatcher:IEventDispatcher = new EventDispatcher();
			
			for (var i:int = 0; i < configs.length; i++) 
			{
				modules[i] = createModuleInfo(configs[i], 
					(views && i > views.length) ? views[i] : null, gDispatcher, null, domain);
				if (parentInfo)
				{
					Group(parentInfo.module.view).addElement(IVisualElement(modules[i].module.view));
				}
				parentInfo = modules[i];
			}
			return new ModuleHierarchy(modules);
		}
		
		public function dispose():void
		{
			if (_moduleContainer is IDestroyable)
				IDestroyable(_moduleContainer).destroy();
		}
		
		private function createModuleConfig(bridge:IBridge, ether:IEther, 
											config:GlobalConfig):ModuleConfig
		{
			var dependencies:IDependencies;
			var mediators:IMediators;
			var wiring:IWiring;
			var commands:ICommands;
			var mediations:IMediations;
			var context:Context = new Default();
			
			if(config.dependenciesFactory)
			{
				dependencies = config.dependenciesFactory.newInstance(context) as IDependencies;
				IModulePart(dependencies).init(bridge);
			}
			if (config.mediatorsFactory)
			{
				mediators = config.mediatorsFactory.newInstance(context) as IMediators;
				IModulePart(mediators).init(bridge);
			}
			if (config.wiringFactory)
			{
				wiring = config.wiringFactory.newInstance(context) as IWiring;
				IModulePart(wiring).init(bridge);
			}
			if (config.commandsFactory)
			{
				commands = config.commandsFactory.newInstance(context) as ICommands;
				IModulePart(commands).init(bridge);
			}
			if (config.mediationFactory)
			{
				mediations = config.mediationFactory.newInstance(context) as IMediations;
			}
			
			return new ModuleConfig(bridge, ether, dependencies, 
				mediators, wiring, commands, mediations);
		}
		
		private function getModuleContainer():IModuleContainer
		{
			if (!_moduleContainer)
			{
				const rootDispatcher:RootDispatcher = new RootDispatcher();
				rootDispatcher.initialize(IEventDispatcher(FlexGlobals.topLevelApplication));
				_moduleContainer = new ModuleContainer();
				ModuleContainer(_moduleContainer).initialize(rootDispatcher);
			}
			
			return _moduleContainer;
		}
	}
}